// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * lombo_motor.c - Driver of LomboTech SPI MOTOR
 *
 * Copyright (C) 2016-2019, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/mutex.h>
#include <linux/math64.h>
#include <linux/spi/spi.h>
#include <mach/debug.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/err.h>

#define ms419xx_cnt  1
#define ms419xx_name  "ms419xx"
#define MORTOR_SIZE  64

struct ms419xx_dev {
	dev_t  devid;
	struct cdev  cdev;
	struct class  *class;
	struct device  *device;
	struct device_node  *nd;
	int major;
	void *private_data;
};

struct lombo_motor {
	struct spi_device  *spi;
	struct ms419xx_dev  *ms419xx_dev;
	struct mutex  lock;
	struct device  dev;

	const char  *name;

	u32  flags;
	u32  freq;
	u32  spi_mode;

	int (*write)(size_t len, const u_char *buf);
};

static struct lombo_motor *motor;

int __write_buf(struct spi_device *spi, size_t buf, u32 len)
{
	int err = 0;
	struct spi_transfer t;
	struct spi_message m;
	struct lombo_spi_operate_para esop;

	memset(&t, 0, sizeof(t));
	memset(&esop, 0, sizeof(esop));
	spi_message_init(&m);

	esop.inst_len_invalid_flag = 1;
	esop.ff = FRAME_FORMAT_STANDARD_SPI;

	t.bits_per_word = 8;
	t.len = len;
	t.tx_buf = (u8 *)buf;
	t.speed_hz = motor->freq;
	t.esop = &esop;

	spi_message_add_tail(&t, &m);

	if (motor->spi_mode == 0)
		spi->mode = SPI_MODE_0;
	else if (motor->spi_mode == 1)
		spi->mode = SPI_MODE_1;
	else if (motor->spi_mode == 2)
		spi->mode = SPI_MODE_2;
	else if (motor->spi_mode == 3)
		spi->mode = SPI_MODE_3;

	err = spi_sync(spi, &m);
	if (err < 0) {
		PRT_ERR("write from:0x%px, len:%u failed\n",
				(size_t *)buf, len);
		return err;
	}

	return err;
}

int lombo_motor_write_buf(struct spi_device *spi, size_t buf, u32 len)
{
	int err = 0;
	size_t src_buf = buf;

	err = __write_buf(spi, src_buf, len);
	if (err) {
		PRT_ERR("__write_buf err, ret=%u\n", err);
		err = __LINE__;
		goto out;
	}

out:
	return err;
}

int lombo_motor_write(size_t len, const u_char *buf)
{
	int err = 0;

	mutex_lock(&motor->lock);
	err  = lombo_motor_write_buf(motor->spi, (size_t)buf, len);
	mutex_unlock(&motor->lock);

	return err;
}


static int ms419xx_open(struct inode *inode, struct file *filp)
{
	filp->private_data = motor->ms419xx_dev;
	return 0;
}

static int ms419xx_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t ms419xx_write(struct file *filp,
		const char __user *buf, size_t size, loff_t *ppos)
{
	u_char kbuf[MORTOR_SIZE];
	int err;

	memset(kbuf, 0, sizeof(kbuf));

	if (size > sizeof(kbuf))
		return -EINVAL;

	err = copy_from_user(kbuf, buf, size);
	if (err)
		return -EINVAL;

	err = lombo_motor_write(size, kbuf);

	return err;
}

static ssize_t ms419xx_read(struct file *filp,
			char __user *buf, size_t size, loff_t *ppos)
{
	return 0;
}

static const struct file_operations ms419xx_ops = {
	.owner = THIS_MODULE,
	.open = ms419xx_open,
	.read = ms419xx_read,
	.write = ms419xx_write,
	.release = ms419xx_release,
};

static int lombo_motor_probe(struct spi_device *spi)
{
	int err;
	struct device_node *np = spi->dev.of_node;

	motor = kzalloc(sizeof(struct lombo_motor), GFP_KERNEL);
	if (!motor) {
		PRT_ERR("malloc motor failed\n");
		err = -ENOMEM;
		goto err_malloc_motor;
	}

	motor->ms419xx_dev = kzalloc(sizeof(struct ms419xx_dev), GFP_KERNEL);
	if (!motor->ms419xx_dev) {
		PRT_ERR("malloc motor->ms419xx_dev failed\n");
		err = -ENOMEM;
		goto err_malloc_motor_ms419_dev;
	}

	err = alloc_chrdev_region(&motor->ms419xx_dev->devid, 0,
				ms419xx_cnt, ms419xx_name);
	if (err < 0) {
		PRT_ERR("Fail to register_chrdev_region\n");
		goto err_register_chrdev_region;
	}

	motor->ms419xx_dev->major = MAJOR(motor->ms419xx_dev->devid);

	motor->ms419xx_dev->class = class_create(THIS_MODULE, ms419xx_name);
	if (IS_ERR(motor->ms419xx_dev->class)) {
		err = PTR_ERR(motor->ms419xx_dev->class);
		PRT_ERR("Fail to class_create\n");
		goto err_class_create;
	}

	cdev_init(&motor->ms419xx_dev->cdev, &ms419xx_ops);
	err = cdev_add(&motor->ms419xx_dev->cdev, motor->ms419xx_dev->devid,
								ms419xx_cnt);
	if (err < 0) {
		PRT_ERR("Fail to add device\n");
		goto err_cdev_add;
	}

	motor->ms419xx_dev->device = device_create(motor->ms419xx_dev->class,
			NULL, motor->ms419xx_dev->devid, NULL, ms419xx_name);
	if (IS_ERR(motor->ms419xx_dev->device)) {
		err = PTR_ERR(motor->ms419xx_dev->device);
		PRT_ERR("Fail to device_create\n");
		goto err_device_create;
	}

	motor->spi = spi;
	mutex_init(&motor->lock);
	dev_set_drvdata(&spi->dev, motor);

	err = of_property_read_u32(np, "spi-max-frequency",
						&motor->freq);
	if (err) {
		PRT_ERR("Fail to get frequency\n");
		goto err_get_frequency;
	}

	err = of_property_read_u32(np, "spi-mode", &motor->spi_mode);
	if (err)
		motor->spi_mode = 0;

	motor->name = dev_name(&spi->dev);
	motor->dev.parent = &spi->dev;
	motor->write = lombo_motor_write;

	PRT_INFO("spi_motor name=%s\n", motor->name);

	return 0;

err_get_frequency:
	device_destroy(motor->ms419xx_dev->class, motor->ms419xx_dev->devid);
err_device_create:
	cdev_del(&motor->ms419xx_dev->cdev);
err_cdev_add:
	class_destroy(motor->ms419xx_dev->class);
err_class_create:
	unregister_chrdev_region(motor->ms419xx_dev->devid, ms419xx_cnt);
err_register_chrdev_region:
	kfree(motor->ms419xx_dev);
err_malloc_motor_ms419_dev:
	kfree(motor);
err_malloc_motor:

	return err;
}

static int lombo_motor_remove(struct spi_device *spi)
{
	device_destroy(motor->ms419xx_dev->class, motor->ms419xx_dev->devid);
	class_destroy(motor->ms419xx_dev->class);

	cdev_del(&motor->ms419xx_dev->cdev);
	unregister_chrdev_region(motor->ms419xx_dev->devid, ms419xx_cnt);

	kfree(motor->ms419xx_dev);
	kfree(motor);

	return 0;
}

static const struct of_device_id lombo_motor_match[] = {
	{ .compatible = "lombo,motor", },
	{},
};
MODULE_DEVICE_TABLE(of, lombo_motor_match);

struct spi_driver lombo_motor_driver = {
	.driver = {
		.name = "lombo_motor",
		.owner = THIS_MODULE,
		.of_match_table = lombo_motor_match,
	},
	.probe = lombo_motor_probe,
	.remove = lombo_motor_remove,
};

static int __init lombo_motor_init(void)
{
	return spi_register_driver(&lombo_motor_driver);
}
module_init(lombo_motor_init);

static void __exit lombo_motor_exit(void)
{
	spi_unregister_driver(&lombo_motor_driver);
}
module_exit(lombo_motor_exit);

MODULE_AUTHOR("lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Driver for LomboTech SPI MOTOR");
MODULE_LICENSE("GPL");

